/**
 * Anderground project - Subway maps processor application.
 * Copyright (C) 2008-2009 Eldar Abusalimov, Ilya Shurigyn
 * 
 * This file is part of Anderground.
 * 
 * Anderground is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * Anderground is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Anderground.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anderground.modules.drawer;

import org.anderground.core.drawer.Calculator;
import org.anderground.core.drawer.MetroDrawer;
import org.anderground.core.drawer.Property;
import org.anderground.core.drawer.Calculator.Mode;
import org.anderground.core.graphics.Canvas;
import org.anderground.core.graphics.Point;
import org.anderground.core.graphics.PointF;
import org.anderground.core.graphics.Rect;
import org.anderground.core.model.ModelData;
import org.anderground.core.model.Station;
import org.anderground.core.model.Transition;

/**
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 * @author Ilya Shurigyn (honorik@gmail.com)
 */
public abstract class BaseDrawer implements MetroDrawer {

	/** the model to be drawn */
	private ModelData modelData;

	/** drawing mode */
	private Mode mode;

	/** drawer properties */
	protected Property[] properties;

	private Rect boundsRect;

	private boolean ready = false;

	@Override
	public final boolean prepare(ModelData modelData, Mode mode) {
		if (modelData == null || mode == null) {
			throw new IllegalArgumentException(
					"modelData or mode cannot be null");
		}
		this.modelData = modelData;
		this.mode = mode;
		properties = new Property[] {};
		boundsRect = null;
		ready = false;
		calculatorApplied = false;

		try {
			prepareImpl();
			Rect boundsRect = getBoundsImpl();
			if (boundsRect == null) {
				boundsRect = new Rect();
			}
			minCoordF.x = boundsRect.getRight();
			minCoordF.y = boundsRect.getTop();
			maxCoordF.x = boundsRect.getLeft();
			maxCoordF.y = boundsRect.getBottom();
			this.boundsRect = boundsRect;
		} catch (Throwable e) {
			return false;
		}

		ready = true;
		return true;
	}

	@Override
	public final void draw(Canvas canvas) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (canvas == null) {
			throw new IllegalArgumentException("canvas cannot be null");
		}
		forceApplyCalculator();
		drawImpl(canvas);
	}

	private Calculator lastAppliedCalculator;

	@Override
	public final void applyCalculator(Calculator calculator) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (calculator == null) {
			throw new IllegalArgumentException("calculator cannot be null");
		}
		if (calculator.getMode() != mode) {
			throw new IllegalArgumentException(
					"calculator's mode must be the same as preparing mode");
		}
		lazyApplyCalculator(calculator);
	}

	/** in optimization purposes */
	private final PointF oldPixelF = new PointF();

	/** converted bounds corner */
	private final PointF minPixelF = new PointF(), maxPixelF = new PointF();

	/** bounds corner */
	private final PointF minCoordF = new PointF(), maxCoordF = new PointF();

	private boolean calculatorApplied = false;

	private void lazyApplyCalculator(Calculator calculator) {
		this.lastAppliedCalculator = calculator;

		PointF oldPixelF = this.oldPixelF;

		oldPixelF.set(minPixelF);
		calculator.convert(minCoordF, minPixelF);
		if (calculatorApplied && !minPixelF.equals(oldPixelF)) {
			calculatorApplied = false;
		}

		oldPixelF.set(maxPixelF);
		calculator.convert(maxCoordF, maxPixelF);
		if (calculatorApplied && !maxPixelF.equals(oldPixelF)) {
			calculatorApplied = false;
		}

	}

	protected final void forceApplyCalculator() {
		if (!calculatorApplied && lastAppliedCalculator != null) {
			applyCalculatorImpl(lastAppliedCalculator);
			calculatorApplied = true;
		}
	}

	@Override
	public final Station[] searchStations(Point point) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (point == null) {
			throw new IllegalArgumentException("point cannot be null");
		}
		forceApplyCalculator();
		Station[] stations = searchStationsImpl(point);
		return stations == null ? new Station[] {} : stations;
	}

	@Override
	public final void copyCoordBounds(Rect rect) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (rect == null) {
			return;
		}
		if (boundsRect != null) {
			rect.set(boundsRect);
		}
	}

	@Override
	public final void selectStations(Canvas canvas, Station... stations) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (canvas == null) {
			throw new IllegalArgumentException("canvas cannot be null");
		}
		if (stations == null) {
			throw new IllegalArgumentException(
					"array of stations cannot be null");
		}
		forceApplyCalculator();
		selectStationsImpl(canvas, stations);
	}

	@Override
	public final void selectTransitions(Canvas canvas,
			Transition... transitions) {
		if (!ready) {
			throw new IllegalStateException(
					"drawer is not yet ready to perform this action");
		}
		if (canvas == null) {
			throw new IllegalArgumentException("canvas cannot be null");
		}
		if (transitions == null) {
			throw new IllegalArgumentException(
					"array of transitions cannot be null");
		}
		forceApplyCalculator();
		selectTransitionsImpl(canvas, transitions);
	}

	@Override
	public final Mode getMode() {
		return mode;
	}

	@Override
	public final ModelData getModelData() {
		return modelData;
	}

	protected abstract void prepareImpl();

	protected abstract void drawImpl(Canvas canvas);

	protected abstract void applyCalculatorImpl(Calculator calculator);

	protected abstract Station[] searchStationsImpl(Point point);

	protected abstract Rect getBoundsImpl();

	protected abstract void selectStationsImpl(Canvas canvas,
			Station... stations);

	protected abstract void selectTransitionsImpl(Canvas canvas,
			Transition... transitions);

	@Override
	public boolean isReady() {
		return ready;
	}

	@Override
	public String getName() {
		return getClass().getSimpleName();
	}

	@Override
	public Property[] getProperties() {
		return properties;
	}

	@SuppressWarnings("unused")
	private void setProperty(String name, String value) {
		// TODO wtf?
		for (Property property : properties) {
			if (property.getName().equalsIgnoreCase(name)) {
				property.setCurrentValue(value);
			}
		}

	}

	@Override
	public void setProperties(Property[] properties) {
		this.properties = properties;
	}

}
